import http from 'http';
import type * as Koa from 'koa';
import koaProxies from 'koa-proxies';
import type * as KoaProxies from 'koa-proxies';

import config from '../../config';
import Logger from '../../utils/Logger';
import * as Enums from '../../utils/Enums';
import { traceHeaderKey } from '../../middleware/traceHelper';
import headersBuilder from './headersBuilder';
import { filterHeaders } from '../../middleware/accessLogHelper';

export enum ProxyPaths {
  EopBackend = '/api/ctcloud',
  StandaloneBackend = '/api/standalone',
  StandalonePlatformSso = '/api/standalone/sso',
}

export const registerProxies = (app: Koa) => {
  // 经由 /api/ctcloud 转发到 EOP 服务，通过 EOP 服务访问业务服务器 API
  config.eop.url && app.use(koaProxies(ProxyPaths.EopBackend, {
    target: config.eop.url,
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\/api/, ''),
    events: {
      error: (error) => { Logger.warn('Proxy error', error); },
      proxyReq: (proxyRequest, request) => {
        setRequestHeaders(proxyRequest, headersBuilder(request));
        logProxyRequest(proxyRequest, request);
      },
      proxyRes: (proxyResponse, request) => {
        logProxyResponse(proxyResponse, request);
      },
    },
  }));

  // 经由 /api/standalone/sso 访问 platform-sso API
  config.standalone.platformSso && app.use(koaProxies(ProxyPaths.StandalonePlatformSso, {
    target: config.standalone.platformSso,
    changeOrigin: true,
    rewrite: (path) => {
      return path.replace(/^\/api\/standalone\/sso/, '');
    },
    events: {
      error: (error) => { Logger.warn('Proxy error', error); },
      proxyReq: (proxyRequest, request) => {
        setRequestHeaders(proxyRequest, headersBuilder(request, { shouldSkipEop: true }));
        logProxyRequest(proxyRequest, request);
      },
      proxyRes: (proxyResponse, request) => {
        logProxyResponse(proxyResponse, request);
      },
    },
  }));

  // 经由 /api/standalone 直接访问业务服务器 API，不经过 EOP 服务
  config.standalone.businessServices && app.use(koaProxies(ProxyPaths.StandaloneBackend, {
    target: config.standalone.businessServices,
    changeOrigin: true,
    rewrite: (path) => {
      return path.replace(/^\/api\/standalone/, '');
    },
    events: {
      error: (error) => { Logger.warn('Proxy error', error); },
      proxyReq: (proxyRequest, request) => {
        setRequestHeaders(proxyRequest, headersBuilder(request, { shouldSkipEop: true }));
        logProxyRequest(proxyRequest, request);
      },
      proxyRes: (proxyResponse, request) => {
        logProxyResponse(proxyResponse, request);
      },
    },
  }));
};

const setRequestHeaders = (proxyRequest: http.ClientRequest, headers: Record<string, any>) => {
  for (const key of Object.keys(headers)) {
    proxyRequest.setHeader(key, headers[key]);
  }
};

const eopHeaderKeys = [
  'ctyun-eop-ak', 'ctyun-eop-sk',
];
const serviceAuthHeaderKeys = [
  'consoleUrl', 'backend',
  'accessKey', 'contentMD5', 'requestDate', 'hmac',
];

const logProxyRequest = (
  proxyRequest: KoaProxies.IKoaProxiesExtendProxyRequest,
  request: KoaProxies.IKoaProxiesExtendRequest,
) => {
  if (request.method === Enums.HTTPMethods.OPTIONS) {
    return;
  }
  const logContent: Record<string, any> = {
    type: 'proxy-request',
    from: `${request.method} - ${request.oldPath}`,
    to: `${request.method} - ${proxyRequest.protocol}//${proxyRequest.getHeaders().host}${proxyRequest.path}`,
    requestId: proxyRequest.getHeader(traceHeaderKey),
  };
  if (config.isNodeEnvDevelopment()) {
    logContent.headers = filterHeaders(proxyRequest.getHeaders(), [...eopHeaderKeys, ...serviceAuthHeaderKeys]);
  }
  Logger.info(logContent);
};

const logProxyResponse = (
  proxyResponse: KoaProxies.IKoaProxiesExtendProxyResponse,
  request: KoaProxies.IKoaProxiesExtendRequest,
) => {
  if (request.method === Enums.HTTPMethods.OPTIONS) {
    return;
  }
  const proxyRequest = proxyResponse.req;
  const responseCode = proxyResponse.statusCode;
  const logContext: Record<string, string | string[] | number | undefined> = {
    type: 'proxy-response',
    from: `${request.method} - ${request.oldPath}`,
    to: `${request.method} - ${proxyRequest?.protocol}//${proxyRequest?.getHeaders().host}${proxyRequest?.path}`,
    requestId: proxyRequest?.getHeader(traceHeaderKey),
    status: responseCode,
  };
  if (responseCode && responseCode >= 400) {
    let responseBody = '';
    proxyResponse.on('data', (chunk) => {
      responseBody += chunk;
    });
    proxyResponse.on('end', () => {
      Logger.info({
        ...logContext, body: responseBody,
      });
    });
  } else {
    Logger.info(logContext);
  }
};
